Skip to content

PYTHON-3606 - Document best practice for closing MongoClients and cursors #2465

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

NoahStapp
Copy link
Contributor

Note: this PR contains both documentation changes and behavioral changes. Only one of these approaches should be ultimately taken, but both are included here for easy comparison.

@NoahStapp NoahStapp requested a review from a team as a code owner August 8, 2025 13:21
raise StopAsyncIteration
if len(self._data) or await self._refresh():
return self._data.popleft()
else:
if self._cursor_type == CursorType.NON_TAILABLE:
await self.close()
Copy link
Member

@ShaneHarvey ShaneHarvey Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this close is redundant since _send_message() already calls close after receiving the final batch. Or is there another case where the cursor would be left open?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the use case when a cursor is not automatically closed (outside of a context manager) is when a user doesn't fully iterate through it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah only an non-exhausted cursor is left open. Even then the cursor is closed at a later point by our GC hooks (via the kill cursor thread) but closing explicitly has stronger guarantees.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, I think a docstring update alone is sufficient. Explicitly calling out that if you don't fully iterate a cursor, you should close it explicitly feels much more actionable than "always use a context manager".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs only SGTM. It could explain the close behavior and then say something like "The best practice to ensure the cursor is always closed promptly is to use a context manager..."

@@ -1263,10 +1263,14 @@ async def next(self) -> _DocumentType:
self._exhaust_checked = True
await self._supports_exhaust()
if self._empty:
if self._cursor_type == CursorType.NON_TAILABLE:
await self.close()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a bug fix? If so should we create a new bug ticket for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll open a ticket for this. Empty cursors should always be immediately closed.

Best practice is to call :meth:`AsyncMongoClient.close` when the client is no longer needed,
or use the client in a with statement::

with AsyncMongoClient(url) as client:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be "async with"?

@NoahStapp NoahStapp requested a review from ShaneHarvey August 8, 2025 16:13
@@ -1776,6 +1776,15 @@ def find(self, *args: Any, **kwargs: Any) -> AsyncCursor[_DocumentType]:
improper type. Returns an instance of
:class:`~pymongo.asynchronous.cursor.AsyncCursor` corresponding to this query.
Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include this note in any other places? Like Cursor or CommandCursor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We explicitly say users shouldn't create those classes directly. Is adding this still useful for our own internal documentation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering because this is only in find() but there are other methods that create cursors, the most important being aggregate().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll add it to all public methods that return a Cursor or CommandCursor.

@NoahStapp NoahStapp requested a review from ShaneHarvey August 11, 2025 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants